<template>
  <!-- <div id="container">
  </div> -->
</template>

<script setup>
import * as THREE from 'three'
import { onMounted } from 'vue';
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
//引入性能监视器stats.js
import Stats from 'three/addons/libs/stats.module.js';
// 引入dat.gui.js的一个类GUI
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

// 实例化一个gui对象
const gui = new GUI();
//改变交互界面style属性
gui.domElement.style.right = '0px';
gui.domElement.style.width = '300px';

//创建一个对象，对象属性的值可以被GUI库创建的交互界面改变
// const obj = {
//   x: 30,
// };
// // gui增加交互界面，用来改变obj对应属性
// gui.add(obj, 'x', 0, 100);
// setInterval(function () {
//   console.log('x', obj.x);
// }, 1000)

const scene = new THREE.Scene()

//创建一个长方体几何对象Geometry
// //BoxGeometry：长方体
// const geometry = new THREE.BoxGeometry(50, 50, 50);
//创建一个空的几何体对象
const geometry = new THREE.BoxGeometry(100, 100, 100);

const material = new THREE.MeshBasicMaterial({
  color: 0x0000ff, //材质颜色
  // wireframe: true //线条模式渲染mesh对应的三角形数据
  // side: THREE.FrontSide, //默认只有正面可见
});
const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
const mesh1 = new THREE.Mesh(geometry, material); //网格模型对象Mesh
mesh1.translateX(200)
// mesh1.position.x = 300
const group = new THREE.Group()
// .add()方法可以单独插入一个对象，也可以同时插入多个子对象。
group.add(mesh)
group.add(mesh1)
// group.add(mesh1, mesh2);
// 在XOZ平面上分布
// mesh.position.set(0, 0, 0);
// scene.add(group); //点模型添加到场景中

//父对象旋转，子对象跟着旋转
// 父对象旋转缩放平移变换，子对象跟着变化
//沿着Y轴平移mesh1和mesh2的父对象，mesh1和mesh2跟着平移
group.translateY(100);

//父对象缩放，子对象跟着缩放
// group.scale.set(4, 4, 4);

//父对象旋转，子对象跟着旋转
group.rotateY(Math.PI / 6)


// 批量创建多个长方体表示高层楼
// 模型命名(.name属性)

// 在层级模型中可以给一些模型对象通过.name属性命名进行标记
const group1 = new THREE.Group(); //所有高层楼的父对象
group1.name = "高层";
for (let i = 0; i < 5; i++) {
  const geometry = new THREE.BoxGeometry(20, 60, 10);
  const material = new THREE.MeshLambertMaterial({
    color: 0x00ffff
  });
  const mesh5 = new THREE.Mesh(geometry, material);
  mesh5.position.x = i * 30; // 网格模型mesh沿着x轴方向阵列
  group1.add(mesh5); //添加到组对象group1
  mesh5.name = i + 1 + '号楼';
  // console.log('mesh.name',mesh.name);
}
group1.position.y = 30;

const group2 = new THREE.Group();
group2.name = "洋房";
// 批量创建多个长方体表示洋房
for (let i = 0; i < 5; i++) {
  const geometry = new THREE.BoxGeometry(20, 30, 10);
  const material = new THREE.MeshLambertMaterial({
    color: 0x00ffff
  });
  const mesh6 = new THREE.Mesh(geometry, material);
  mesh6.position.x = i * 30;
  group2.add(mesh6); //添加到组对象group2
  mesh6.name = i + 6 + '号楼';
}
group2.position.z = 50;
group2.position.y = 15;

const model = new THREE.Group();
model.name = '小区房子';
model.add(group1, group2);
model.position.set(-50, 0, -25);
scene.add(model)

// 递归遍历方法.traverse()

// Threejs层级模型就是一个树结构，可以通过递归遍历的算法去遍历Threejs一个模型对象包含的所有后代。

// 递归遍历model包含所有的模型节点
model.traverse(function (obj) {
  console.log('所有模型节点的名称', obj.name, obj.isMesh);
  // obj.isMesh：if判断模型对象obj是不是网格模型'Mesh'
  if (obj.isMesh) {//判断条件也可以是obj.type === 'Mesh'
    obj.material.color.set(0xffff00);
  }
});
// 查找某个具体的模型.getObjectByName()

// 看到Object3D的.getObjectByName()方法，如果已有前端基础，很容易联想到DOM的一些方法。

// Threejs和前端DOM一样，可以通过一个方法查找树结构父元素的某个后代对象，对于普通前端而言可以通过name或id等方式查找一个或多个DOM元素，Threejs同样可以通过一些方法查找一个模型树中的某个节点。更多的查找方法和方法的使用细节可以查看基类Object3D。

// 返回名.name为"4号楼"对应的对象
const nameNode = model.getObjectByName("4号楼");
// const nameNode = scene.getObjectByName("4号楼");
nameNode.material.color.set(0xff0000);


// Object3D表示模型对象节点

// 受threejs历史影响，你会在很多别的代码中看到Object3D作为Group来使用，如果看到不用奇怪。某种程度上，你可把两者画等号，只是Group更加语义化，
// Object3D本身就是表示模型节点的意思。

// const mesh1 = new THREE.Mesh(geometry, material);
// const mesh2 = new THREE.Mesh(geometry, material);
// const obj = new THREE.Object3D();//作为mesh1和mesh2的父对象
// obj.add(mesh1, mesh2);

//  mesh也能添加mesh子对象

// threejs默认mesh也可以添加子对象,其实原因很简单，mesh和Group父类都是Object3D，本质上也可以认为都是Object3D。

//threejs默认mesh也可以添加子对象,mesh基类也是Object3D
// mesh1.add(mesh)


// 查看子对象.children

// Threejs场景对象Scene、组对象Group都有一个子对象属性.children, 通过该属性可以访问父对象的子对象，子对象属性.children的值是数组，所有子对象是数组的值，你可以在浏览器控制台打印测试上面案例代码。

// 父对象执行.add()方法的本质就是把参数中的子对象添加到自身的子对象属性.children中。

// console.log('查看group的子对象', group.children);

// // #
// // 场景对象结构

// console.log('查看Scene的子对象', scene.children);


//点光源：两个参数分别表示光源颜色和光照强度
// 参数1：0xffffff是纯白光,表示光源颜色
// 参数2：1.0,表示光照强度，可以根据需要调整
const pointLight = new THREE.PointLight(0xffffff, 1.0);
pointLight.decay = 0.0;//设置光源不随距离衰减
//点光源位置
pointLight.position.set(200, 200, 300);//点光源放在x轴上
scene.add(pointLight); //点光源添加到场景中

//环境光:没有特定方向，整体改变场景的光照明暗
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);

// 平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
// 设置光源的方向：通过光源position属性和目标指向对象的position属性计算
directionalLight.position.set(-80, -100, -50);
// 方向光指向对象网格模型mesh，可以不设置，默认的位置是0,0,0
directionalLight.target = mesh;
scene.add(directionalLight);
// DirectionalLightHelper：可视化平行光
const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5, 0xff0000);
scene.add(dirLightHelper);



// 点光源辅助可视化观察
const pointLightHelper = new THREE.PointLightHelper(pointLight, 10);
scene.add(pointLightHelper);


// AxesHelper：辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(300);
scene.add(axesHelper);

const obj2 = {
  bool: false,
};

const width1 = 800; //宽度
const height1 = 500; //高度
// 实例化一个透视投影相机对象
// 30:视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000：远裁截面
// // 超出视锥体远裁界面的范围的会被剪裁掉，不渲染  可以调整far参数适配 所以要看全部就把远裁面拉高10000
const camera = new THREE.PerspectiveCamera(30, width1 / height1, 1, 10000);

//相机在Three.js三维坐标系中的位置
// 根据需要设置相机位置具体值
camera.position.set(500, 500, 500);
// camera.lookAt(mesh.position);//指向mesh对应的位置
// // 改变相机观察目标点
camera.lookAt(250, 0, 250);

// width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
// const width = 800; //宽度
// const height = 500; //高度
const width = window.innerWidth; //宽度
const height = window.innerHeight; //高度
// 创建渲染器对象
// 设置渲染器锯齿属性.antialias的值可以直接在参数中设置，也可通过渲染器对象属性设置
const renderer = new THREE.WebGLRenderer({
  antialias: true,
});
// 或者直接设置参数  renderer.antialias = true


// 不同硬件设备的屏幕的设备像素比window.devicePixelRatio值可能不同
console.log('查看当前屏幕设备像素比', window.devicePixelRatio);

// 如果你遇到你的canvas画布输出模糊问题，注意设置renderer.setPixelRatio(window.devicePixelRatio)。
// 注意：注意你的硬件设备设备像素比window.devicePixelRatio刚好是1，那么是否执行.setPixelRatio()不会有明显差异，不过为了适应不同的硬件设备屏幕，通常需要执行该方法。
// 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题
renderer.setPixelRatio(window.devicePixelRatio);

renderer.setClearColor(0x444444, 1); //设置背景颜色

renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
renderer.render(scene, camera); //执行渲染操作
onMounted(() => {
  // document.getElementById('container').appendChild(renderer.domElement);
  document.body.appendChild(renderer.domElement);
})

// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);

// // 改变相机观察目标点 camera.lookAt(250, 0, 250);
// 意相机控件OrbitControls会影响lookAt设置，注意手动设置OrbitControls的目标参数
controls.target.set(250, 0, 250)
controls.update()


// 如果OrbitControls改变了相机参数，重新调用渲染器渲染三维场景
controls.addEventListener('change', function () {
  // 浏览器控制台查看相机位置变化
  // console.log('camera.position', camera.position);
  // 渲染循环和相机控件OrbitControls

  // 设置了渲染循环, 相机控件OrbitControls就不用再通过事件change执行renderer.render(scene, camera);，毕竟渲染循环一直在执行renderer.render(scene, camera);。
  // renderer.render(scene, camera); //执行渲染操作
});//监听鼠标、键盘事件

const clock = new THREE.Clock();
//创建stats对象
const stats = new Stats();
//stats.domElement:web页面上输出计算结果,一个div元素，
document.body.appendChild(stats.domElement);
function render() {
  // const spt = clock.getDelta() * 1000;//毫秒
  // console.log('两帧渲染时间间隔(毫秒)', spt);
  // console.log('帧率FPS', 1000 / spt);
  stats.update();
  renderer.render(scene, camera); //执行渲染操作

  // mesh.rotateY(0.01);// mesh旋转动画
  // // 同步mesh2和mesh的姿态角度一样，不管mesh姿态角度怎么变化，mesh2始终保持同步
  // mesh2.rotation.copy(mesh.rotation);

  // mesh.rotation.y += 0.01;
  // mesh.rotation.y += 0.01;//每次绕y轴旋转0.01弧度
  // mesh.rotateY(0.01); // GUI进行单选框控制是否旋转
  // mesh.rotateY(0.01);//每次绕y轴旋转0.01弧度
  requestAnimationFrame(render);//请求再次执行渲染函数render，渲染下一帧
}
render();
// onresize 事件会在窗口被调整大小时发生
window.onresize = function () {
  // 重置渲染器输出画布canvas尺寸
  renderer.setSize(window.innerWidth, window.innerHeight);
  // 全屏情况下：设置观察范围长宽比aspect为窗口宽高比
  camera.aspect = window.innerWidth / window.innerHeight;
  // 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
  // 但是不会每渲染一帧，就通过相机的属性计算投影矩阵(节约计算资源)
  // 如果相机的一些属性发生了变化，需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
  camera.updateProjectionMatrix();
};



</script>
<style scoped>
#container {
  width: 500px;
  height: 500px;
  margin: 100px auto;
  background: gray;
}
</style>